#!/bin/bash
echo "DO NOT USE A PERIOD IN THE DATABASE NAME"
echo "use an underscore in place of a period"
echo "What is your domain name?"
read domain
DB="$domain"
create random database user
DBUSER="$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 12 | head -n 1)"
create random password
PASSWORD="$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 12 | head -n 1)"
create random database prefix
PREFIX="$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 4 | head -n 1)"
mysql <
angelscript
⚠️ Critical Warning: DO NOT use periods (.) in database names. Always use
underscores (_) instead. For example, use example_com instead
of example.com
✅ Security Features:
- Randomly generated 12-character database username
- Randomly generated 12-character password
- Random 4-character table prefix (helps prevent SQL injection attacks)
- Credentials displayed only once - save them immediately!
Script 3: Admin Credentials & WordPress Constants
This script generates secure admin credentials and WordPress security salts.
nano 3.admin_credentials.sh
#!/bin/bash
Generate and display WordPress admin username and password
echo "Randomly Generated 30 Character WordPress Administrative Credentials"
echo ""
echo "WordPress Admin Username: $(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 30 | head -n
1)"
echo "WordPress Admin Password: $(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 30 | head -n
1)"
Fetch WordPress salts
wp_salts=$(curl -s https://api.wordpress.org/secret-key/1.1/salt/)
Add WP constants
wp_constants=$(cat << 'EOF' /** Allow Direct Updating Without FTP / define('FS_METHOD', 'direct'
); /* Disable Editing of Themes and Plugins Using the Built In Editor /
define('DISALLOW_FILE_EDIT', 'true' ); /* TURN OFF AUTOMATIC UPDATES */
define('WP_AUTO_UPDATE_CORE', 'false' ); EOF ) Output the salts and constants echo ""
echo "WordPress Salts" echo "" echo "$wp_salts" echo ""
echo "Constants to be added to wp-config.php" echo "" echo "$wp_constants"
angelscript
💡 Script Features:
- 30-character credentials: Maximum security for admin access
- WordPress Salts: Fetched directly from WordPress API
- Security Constants: Pre-configured for best practices
Script 4: Nginx Server Block Creation
This comprehensive script creates the initial Nginx configuration for your site.
nano 4.nginx_server_block.sh
#!/bin/bash
Function to create directories and files if they don't exist
create_files_and_directories() {
# Check if the includes directory exists, if not create it
if [ ! -d "/etc/nginx/includes" ]; then
sudo mkdir -p /etc/nginx/includes
fi
# Check if fastcgi_optimize.conf exists, if not create it
if [ ! -f "/etc/nginx/includes/fastcgi_optimize.conf" ]; then
sudo tee /etc/nginx/includes/fastcgi_optimize.conf > /dev/null <<EOF
fastcgi_connect_timeout 60;
fastcgi_send_timeout 180;
fastcgi_read_timeout 180;
fastcgi_buffer_size 512k;
fastcgi_buffers 512 16k;
fastcgi_busy_buffers_size 1m;
fastcgi_temp_file_write_size 4m;
fastcgi_max_temp_file_size 4m;
fastcgi_intercept_errors on;
EOF
fi
# Check if browser_caching.conf exists, if not create it
if [ ! -f "/etc/nginx/includes/browser_caching.conf" ]; then
sudo tee /etc/nginx/includes/browser_caching.conf > /dev/null <<EOF
location ~*
.(webp|3gp|gif|jpg|jpeg|png|ico|wmv|avi|asf|asx|mpg|mpeg|mp4|pls|mp3|mid|wav|swf|flv|exe|zip|tar|rar|gz|tgz|bz2|uha|7z|doc|docx|xls|xlsx|pdf|iso)$
{
add_header Cache-Control "public, no-transform";
access_log off;
expires 365d;
}
location ~* .(js)$ {
add_header Cache-Control "public, no-transform";
access_log off;
expires 30d;
}
location ~* .(css)$ {
add_header Cache-Control "public, no-transform";
access_log off;
expires 30d;
}
location ~* .(eot|svg|ttf|woff|woff2)$ {
add_header Cache-Control "public, no-transform";
access_log off;
expires 30d;
}
EOF
fi
}
Function to create domain.com.conf file
create_domain_conf_file() {
domain=$1
filename="/etc/nginx/sites-available/$domain.conf"
perl
# Define server_name without checking for subdomain
server_name="$domain www.$domain"
# Check if the domain.com.conf file exists, if not create it
if [ ! -f "$filename" ]; then
sudo tee "$filename" > /dev/null <<EOF
server {
awk
listen 80;
server_name $server_name;
root /var/www/$domain/public_html;
index index.php;
location / {
try_files \$uri \$uri/ /index.php\$is_args\$args;
}
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
include /etc/nginx/includes/fastcgi_optimize.conf;
}
include /etc/nginx/includes/browser_caching.conf;
access_log /var/log/nginx/access_$domain.log combined buffer=256k flush=60m;
error_log /var/log/nginx/error_$domain.log;
}
EOF
fi
awk
# Create a symbolic link to the sites-enabled directory
sudo ln -s /etc/nginx/sites-available/$domain.conf /etc/nginx/sites-enabled/
}
Main script starts here
Ask for domain name
read -p "Enter domain name: " domain_name
Call function to create directories and files
create_files_and_directories
Call function to create domain.com.conf file
create_domain_conf_file "$domain_name"
Display success message
echo "Configuration files created successfully."
angelscript
📌 What This Script Creates:
- /etc/nginx/includes/fastcgi_optimize.conf - FastCGI performance
settings
- /etc/nginx/includes/browser_caching.conf - Browser cache rules
for static assets
- /etc/nginx/sites-available/domain.conf - Main server block
configuration
- Symbolic link - Links configuration to sites-enabled directory
Making Scripts Executable
Before running any scripts, you must grant them executable permissions:
chmod +x 1.create_wp_directories.sh 2.create_wp_database.sh
3.admin_credentials.sh 4.nginx_server_block.sh
🌐 Section 2: DNS Configuration
Before proceeding with site setup, you must configure DNS records to point your domain to
your server.
Required DNS Records
| Record Type |
Name |
Content/Target |
Proxy Status |
| A Record |
@ (or domain.com) |
Your Server IP Address |
DNS Only (initially) |
| CNAME Record |
www |
domain.com |
DNS Only (initially) |
💡 Cloudflare Users: Set proxy status to "DNS Only" initially. You can
enable "Proxied" status after SSL certificates are installed and working correctly.
Verifying DNS Resolution
After configuring DNS records, verify that your domain resolves correctly to your server:
ping -c 2 example.com
ping -c 2 www.example.com
✅ Expected Result: Both commands should display your server's IP
address, confirming DNS propagation is complete.
📁 Section 3: Creating Site Directories
Now that DNS is configured and our scripts are ready, let's create the directory
structure for the new site.
Navigate to Scripts Directory
cd ~/wp_bash_scripts/
Run Directory Creation Script
./1.create_wp_directories.sh
💡 Script Execution: When prompted, enter your domain name exactly as
configured in DNS (e.g., example.com). The script will create:
- /var/www/example.com/public_html/ - Web root
directory
- /var/www/example.com/tmp/ - Temporary files
directory
🗄️ Section 4: Database Setup
Create a dedicated MySQL database with secure, randomly generated credentials.
Run Database Creation Script
./2.create_wp_database.sh
⚠️ Critical: When entering the domain name, replace periods with
underscores:
- Correct: example_com
- Incorrect: example.com
✅ Save These Credentials Immediately: The script will display:
- Database Name
- Database Username (random 12 characters)
- Database Password (random 12 characters)
- Table Prefix (random 4 characters)
Generate Admin Credentials
./3.admin_credentials.sh
💡 What You'll Receive:
- Random 30-character WordPress admin username
- Random 30-character WordPress admin password
- WordPress security salts (8 unique keys)
- Recommended wp-config.php constants
⚙️ Section 5: Nginx Server Block Configuration
Create the Nginx configuration that will serve your WordPress site.
Run Server Block Creation Script
./4.nginx_server_block.sh
When prompted, enter your domain name (e.g., example.com). The script will automatically
configure server blocks for both example.com and www.example.com.
Test and Reload Nginx
After creating the server block, always test the configuration before reloading:
sudo nginx -t
✅ Expected Output:
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
angelscript
sudo systemctl reload nginx
📦 Section 6: WordPress Installation
Now we'll download, configure, and install WordPress for the new site.
Download WordPress
cd ~
wget https://wordpress.org/latest.tar.gz
tar xf latest.tar.gz
cd wordpress/
Configure wp-config.php
mv wp-config-sample.php wp-config.php
nano wp-config.php
💡 Required Changes in wp-config.php:
- Database Name: Enter the database name from the script output
- Database User: Enter the random username generated
- Database Password: Enter the random password generated
- Database Prefix: Change $table_prefix to the random prefix + underscore
- Authentication Keys & Salts: Replace with the salts from script
output
- Security Constants: Add the constants provided by the script
Copy Files to Site Directory
cd ~
rsync -artv wordpress/ /var/www/example.com/public_html/
Set Proper Ownership
cd /var/www/example.com/
sudo chown -R www-data:www-data public_html/
ls -l
Complete Installation via Browser
Open your web browser and navigate to http://example.com
to complete the WordPress installation through the web interface.
📌 Installation Steps:
- Select your language
- Enter site title, admin username (from script), admin password (from script),
and admin email
- Complete the installation
- Log in to WordPress dashboard
🔒 Section 7: Security Hardening
Implementing multiple layers of security to protect your WordPress installation.
PHP-FPM Pool Configuration
Creating isolated PHP processes for each site enhances security and performance.
Create Dedicated User
sudo useradd example_user
sudo usermod -a -G example_user www-data
sudo usermod -a -G www-data example_user
sudo usermod -a -G $USER example_user
Create PHP Pool Configuration
cd /etc/php/8.3/fpm/pool.d/
sudo cp www.conf example.com.conf
sudo nano example.com.conf
; pool name
[example]
; Username and Group
user = example_user
group = example_user
; Path and Unix Socket Filename - unique to this pool
listen = /run/php/php8.3-fpm-example.com.sock
; Set open file descriptor rlimit.
rlimit_files = 15000
; Set max core size rlimit.
rlimit_core = 100
; PHP POOL CONFIGURATION
php_flag[display_errors] = off
php_admin_value[error_log] = /var/log/fpm-php.example.log
php_admin_flag[log_errors] = on
angelscript
Create PHP-FPM Log File
sudo touch /var/log/fpm-php.example.log
sudo chown example_user:www-data /var/log/fpm-php.example.log
sudo chmod 660 /var/log/fpm-php.example.log
Update Nginx Configuration
sudo nano /etc/nginx/sites-available/example.com.conf
💡 Modify the PHP location block: Change the fastcgi_pass directive to
use the new socket:
fastcgi_pass unix:/run/php/php8.3-fpm-example.com.sock;
Test and Reload Services
sudo nginx -t
sudo systemctl reload nginx
sudo systemctl reload php8.3-fpm
File Permissions & Ownership
Set Ownership
cd /var/www/example.com/
sudo chown -R example_user:example_user public_html/ tmp/
Initial Permissions (for updates)
sudo chmod 770 public_html/
sudo chmod 770 tmp/
sudo find /var/www/example.com/public_html/ -type d -exec chmod
770 {} \;
sudo find /var/www/example.com/public_html/ -type f -exec chmod
660 {} \;
Hardened Permissions (for production)
sudo find /var/www/example.com/public_html/ -type d -exec chmod
550 {} \;
sudo find /var/www/example.com/public_html/ -type f -exec chmod
440 {} \;
sudo find /var/www/example.com/public_html/wp-content/ -type d
-exec chmod 770 {} \;
sudo find /var/www/example.com/public_html/wp-content/ -type f
-exec chmod 660 {} \;
⚠️ Permission Levels:
- 770/660: Allows WordPress dashboard updates (loosen before
updates)
- 550/440: Read-only for enhanced security (tighten after
updates)
- wp-content always 770/660: Required for uploads and
plugin/theme operations
Disable Dangerous PHP Functions
sudo nano /etc/php/8.3/fpm/pool.d/example.com.conf
; DISABLED FUNCTIONS
php_admin_value[disable_functions] = shell_exec, opcache_get_configuration,
opcache_get_status, disk_total_space, diskfreespace, dl, exec, passthru, pclose,
pcntl_alarm, pcntl_exec, pcntl_fork, pcntl_get_last_error, pcntl_getpriority,
pcntl_setpriority, pcntl_signal, pcntl_signal_dispatch, pcntl_sigprocmask,
pcntl_sigtimedwait, pcntl_sigwaitinfo, pcntl_strerror, pcntl_waitpid, pcntl_wait,
pcntl_wexitstatus, pcntl_wifcontinued, pcntl_wifexited, pcntl_wifsignaled,
pcntl_wifstopped, pcntl_wstopsig, pcntl_wtermsig, popen, posix_getpwuid, posix_kill,
posix_mkfifo, posix_setpgid, posix_setsid, posix_setuid, posix_uname, proc_close,
proc_get_status, proc_nice, proc_open, proc_terminate, show_source, system
angelscript
sudo systemctl reload php8.3-fpm
Open_basedir Restrictions
Restricting PHP file access to only necessary directories prevents unauthorized file
access.
sudo nano /etc/php/8.3/fpm/pool.d/example.com.conf
php_admin_value[upload_tmp_dir] = /var/www/example.com/tmp/
php_admin_value[sys_temp_dir] = /var/www/example.com/tmp/
php_admin_value[open_basedir] =
/var/www/example.com/public_html/:/var/www/example.com/tmp/
angelscript
Database Privileges Restriction
Limit database user privileges to only what WordPress needs.
Verify Database Credentials
sudo grep DB_NAME /var/www/example.com/public_html/wp-config.php
sudo grep DB_USER /var/www/example.com/public_html/wp-config.php
Restrict Privileges
mysql
REVOKE ALL PRIVILEGES ON database_name.* FROM
'username'@'localhost';
GRANT SELECT, INSERT, UPDATE, DELETE ON database_name.* TO 'username'@'localhost';
FLUSH PRIVILEGES;
EXIT;
angelscript
💡 Why Restrict Privileges? WordPress only needs SELECT, INSERT,
UPDATE, and DELETE for normal operation. Removing CREATE, DROP, and ALTER privileges
prevents malicious code from modifying database structure.
🔐 Section 8: SSL/TLS Certificate Configuration
Secure your site with free Let's Encrypt SSL certificates.
Obtain SSL Certificate
sudo certbot certonly --webroot -w
/var/www/example.com/public_html/ -d example.com -d www.example.com
Create SSL Configuration File
sudo nano /etc/nginx/ssl/ssl_certs_example.com.conf
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
SSL STAPLING
ssl_trusted_certificate /etc/letsencrypt/live/example.com/chain.pem;
angelscript
Update Server Block for HTTPS
sudo nano /etc/nginx/sites-available/example.com.conf
server {
listen 80;
server_name example.com www.example.com;
# 301 Permanent Redirect to HTTPS
return 301 https://example.com$request_uri;
}
server {
listen 443 ssl;
http2 on;
gradle
listen 443 quic;
http3 on;
server_name example.com www.example.com;
root /var/www/example.com/public_html;
index index.php;
# Include SSL configurations
include /etc/nginx/ssl/ssl_certs_example.com.conf;
include /etc/nginx/ssl/ssl_all_sites.conf;
location / {
try_files $uri $uri/ /index.php$is_args$args;
}
# Place HTTP headers ABOVE php processing block
include /etc/nginx/includes/http_headers.conf;
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_param HTTP_HOST $host;
fastcgi_pass unix:/run/php/php8.3-fpm-example.com.sock;
include /etc/nginx/includes/fastcgi_optimize.conf;
}
include /etc/nginx/includes/browser_caching.conf;
access_log /var/log/nginx/access_example.com.log combined buffer=256k flush=60m;
error_log /var/log/nginx/error_example.com.log;
}
angelscript
Test and Reload
sudo nginx -t
sudo systemctl reload nginx
Verify SSL Configuration
Test your redirects and HTTPS configuration:
curl -I http://example.com
curl -I http://www.example.com
curl -I https://www.example.com
curl -I https://example.com
✅ Expected Results: All HTTP requests should show 301 redirects to
https://example.com
SSL Testing Resources
📌 Test Your SSL Configuration:
⚡ Section 9: Performance Optimization
WordPress Core Optimizations
Disable Post Revisions
Add to wp-config.php:
define('WP_POST_REVISIONS', false);
Increase Memory Limit
Add to wp-config.php:
/** MEMORY LIMIT */
define('WP_MEMORY_LIMIT', '256M');
angelscript
Update PHP pool configuration:
sudo nano /etc/php/8.3/fpm/pool.d/example.com.conf
php_admin_value[memory_limit] = 256M
Optimize WP-Cron
Disable default WP-Cron in wp-config.php:
/** DISABLE WP-CRON */
define('DISABLE_WP_CRON', true);
angelscript
Set up system cron job:
crontab -e
*/15 * * * * wget -q -O -
https://example.com/wp-cron.php?doing_wp_cron >/dev/null 2>&1
OPcache Configuration
sudo nano /etc/php/8.3/fpm/pool.d/example.com.conf
; OPCACHE CONFIGURATION - DEVELOPMENT SERVER
php_admin_value[opcache.memory_consumption] = 256
php_admin_value[opcache.interned_strings_buffer] = 32
php_admin_value[opcache.max_accelerated_files] = 20000
php_admin_flag[opcache.validate_timestamps] = 1
php_admin_value[opcache.revalidate_freq] = 2
php_admin_flag[opcache.validate_permission] = 1
; OPCACHE CONFIGURATION - PRODUCTION SERVER
;php_admin_value[opcache.memory_consumption] = 256
;php_admin_value[opcache.interned_strings_buffer] = 32
;php_admin_value[opcache.max_accelerated_files] = 20000
;php_admin_flag[opcache.validate_timestamps] = 0
;php_admin_flag[opcache.validate_permission] = 1
angelscript
💡 OPcache Settings:
- Development: validate_timestamps = 1 (checks for file changes)
- Production: validate_timestamps = 0 (maximum performance,
manual cache clearing required)
FastCGI Caching
Configure Global FastCGI Cache
sudo nano /etc/nginx/nginx.conf
### FASTCGI CACHING
fastcgi_cache_path directive - PATH & NAME must be unique for each site
fastcgi_cache_path /var/run/example_cache levels=1:2 keys_zone=EXAMPLECACHE:100m
inactive=60m;
fastcgi_cache_path /var/run/example2_cache levels=1:2 keys_zone=EXAMPLE2CACHE:100m
inactive=60m;
applied to all sites
fastcgi_cache_key "$scheme$request_method$host$request_uri";
fastcgi_cache_use_stale error timeout invalid_header http_500;
fastcgi_ignore_headers Cache-Control Expires Set-Cookie;
angelscript
Update Server Block for Caching
sudo nano /etc/nginx/sites-available/example.com.conf
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_param HTTP_HOST $host;
fastcgi_pass unix:/run/php/php8.3-fpm-example.com.sock;
include /etc/nginx/includes/fastcgi_optimize.conf;
# fastcgi caching directives
fastcgi_cache_bypass $skip_cache;
fastcgi_no_cache $skip_cache;
fastcgi_cache EXAMPLECACHE;
fastcgi_cache_valid 60m;
}
include /etc/nginx/includes/fastcgi_cache_excludes.conf;
add_header X-FastCGI-Cache $upstream_cache_status;
Cache purge location
location ~ /purge(/.*) {
fastcgi_cache_purge EXAMPLECACHE "$scheme$request_method$host$1";
}
angelscript
sudo nginx -t
sudo systemctl reload nginx
Rate Limiting
Protect wp-login.php and xmlrpc.php from brute force attacks.
cd /etc/nginx/includes/
sudo cp rate_limiting_example.com.conf
rate_limiting_example.net.conf
sudo nano rate_limiting_example.net.conf
location = /wp-login.php {
limit_req zone=wp burst=20 nodelay;
limit_req_status 444;
include snippets/fastcgi-php.conf;
fastcgi_param HTTP_HOST $host;
fastcgi_pass unix:/run/php/php8.3-fpm-example.com.sock;
include /etc/nginx/includes/fastcgi_optimize.conf;
}
location = /xmlrpc.php {
limit_req zone=wp burst=20 nodelay;
limit_req_status 444;
include snippets/fastcgi-php.conf;
fastcgi_param HTTP_HOST $host;
fastcgi_pass unix:/run/php/php8.3-fpm-example.com.sock;
include /etc/nginx/includes/fastcgi_optimize.conf;
}
Include in server block:
# Rate Limiting Include
include /etc/nginx/includes/rate_limiting_example.net.conf;
angelscript
🔧 Section 10: Maintenance Scripts
Script: Loosen Permissions (for updates)
nano ~/wp_bash_scripts/5.loosen_permissions.sh
#!/bin/bash
List directory contents of /var/www for reference
sudo ls -l /var/www
Prompt for the domain name
read -p "Enter the domain name: " domain_name
Check if the domain directory exists
if [ ! -d "/var/www/$domain_name/public_html/" ]; then
echo "Error: Domain directory /var/www/$domain_name/public_html/ does not exist."
exit 1
fi
Set permissions with confirmation
echo "Setting permissions for /var/www/$domain_name/public_html/..."
Grant read, write, and execute permissions to owner and group - directories
sudo find "/var/www/$domain_name/public_html/" -type d -exec chmod 770 {} ;
Grant read and write permissions to owner and group - files
sudo find "/var/www/$domain_name/public_html/" -type f -exec chmod 660 {} ;
echo "Permissions set to allow WordPress updates using the dashboard!"
angelscript
Script: Tighten Permissions (after updates)
nano ~/wp_bash_scripts/6.tighten_permissions.sh
#!/bin/bash
List directory contents of /var/www for reference
sudo ls -l /var/www
Prompt for the domain name
read -p "Enter the domain name: " domain_name
Check if the domain directory exists
if [ ! -d "/var/www/$domain_name/public_html/" ]; then
echo "Error: Domain directory /var/www/$domain_name/public_html/ does not exist."
exit 1
fi
Set permissions with confirmation
echo "Setting permissions for /var/www/$domain_name/public_html/..."
Grant read and execute permissions to the owner and group only
sudo find "/var/www/$domain_name/public_html/" -type d -exec chmod 550 {} ;
Grant read permissions to the owner and group only
sudo find "/var/www/$domain_name/public_html/" -type f -exec chmod 440 {} ;
Grant read, write and execute permissions to the owner and group only - wp-content/
sudo find "/var/www/$domain_name/public_html/wp-content" -type d -exec chmod 770 {} ;
Grant read, write to the owner and group only - wp-content/
sudo find "/var/www/$domain_name/public_html/wp-content/" -type f -exec chmod 660 {} ;
echo "Permissions hardened, no write permissions on core WordPress files and
directories!"
angelscript
chmod +x ~/wp_bash_scripts/5.loosen_permissions.sh
~/wp_bash_scripts/6.tighten_permissions.sh
Monitoring Scripts
PHP Pool Memory Usage
nano ~/wp_bash_scripts/pool_list_usage.sh
#!/bin/bash
Extract pool names
pools=$(grep -E '^\suser\s=' /etc/php/8.3/fpm/pool.d/*.conf | awk -F= '{print $2}' |
xargs | tr ' ' '\n' | sort -u)
Display memory used by each pool
for pool in $pools; do
memory_used=$(ps -C php-fpm --user "$pool" -o rss= | awk '{ sum += $1; count++ } END {
if (count > 0) printf ("%d%s\n", sum/NR/1024,"M") }')
echo -e "Pool: $pool\nMemory Used: $memory_used\n"
done
angelscript
OPcache File Count Monitor
nano ~/wp_bash_scripts/opcache_files.sh
#!/bin/bash
echo ""
echo "Checking for and displaying the opcache.max_accelerated_files directive value in
each sites PHP pool file"
echo ""
POOL_DIR="/etc/php/8.3/fpm/pool.d/"
for file in "$POOL_DIR"*.conf; do
filename=$(basename "$file")
if grep -q "^[^#;]*opcache.max_accelerated_files" "$file"; then
value=$(grep "^[^#;]*opcache.max_accelerated_files" "$file" | awk -F'=' '{print $2}' |
tr -d ' ')
echo "File: $filename - opcache.max_accelerated_files: $value"
else
echo "File: $filename - opcache.max_accelerated_files directive not found"
fi
done
echo ""
echo "Listing sites and number of PHP files in each site, compare to the value set in
the pool file"
echo ""
WWW_DIR="/var/www/"
for domain_dir in "$WWW_DIR"*/ ; do
if [ "$domain_dir" == "/var/www/html/" ]; then
continue
fi
routeros
domain_name=$(basename "$domain_dir")
public_html_dir="$domain_dir/public_html/"
if [ -d "$public_html_dir" ]; then
php_file_count=$(find "$public_html_dir" -type f -name "*.php" | wc -l)
echo "Domain: $domain_name - PHP files: $php_file_count"
else
echo "Domain: $domain_name - public_html directory not found"
fi
done
echo ""
angelscript
chmod +x ~/wp_bash_scripts/pool_list_usage.sh
~/wp_bash_scripts/opcache_files.sh
📊 Complete Checklist
Pre-Flight Checklist
| Step |
Task |
Status |
| 1 |
DNS A & CNAME records configured |
☐ |
| 2 |
DNS propagation verified (ping test) |
☐ |
| 3 |
Bash scripts created and executable |
☐ |
| 4 |
Site directories created |
☐ |
| 5 |
Database created with credentials saved |
☐ |
| 6 |
Admin credentials generated and saved |
☐ |
| 7 |
Nginx server block configured |
☐ |
| 8 |
WordPress installed and configured |
☐ |
| 9 |
PHP-FPM pool created for site |
☐ |
| 10 |
File permissions hardened |
☐ |
| 11 |
PHP functions disabled |
☐ |
| 12 |
Open_basedir restrictions applied |
☐ |
| 13 |
Database privileges restricted |
☐ |
| 14 |
SSL certificate obtained |
☐ |
| 15 |
HTTPS enforced with redirects |
☐ |
| 16 |
HTTP/2 and HTTP/3 enabled |
☐ |
| 17 |
WordPress core optimizations applied |
☐ |
| 18 |
OPcache configured |
☐ |
| 19 |
FastCGI caching configured |
☐ |
| 20 |
Rate limiting enabled |
☐ |
| 21 |
SSL/TLS tested (SSL Labs) |
☐ |
| 22 |
HTTP/3 tested (HTTP3 Check) |
☐ |
🎯 Key Differences Between Sites
📌 Per-Site Unique Configuration:
- PHP-FPM Pool: Each site must have unique pool name, socket
path, and system user
- FastCGI Cache: Each site must have unique cache path and
keys_zone name
- Nginx Server Block: Each site must have unique server_name and
root directory
- SSL Certificates: Each domain requires its own certificate (can
use same cert for www subdomain)
- Log Files: Each site should have separate access and error logs
⚠️ Common Pitfalls & Solutions
| Issue |
Cause |
Solution |
| 502 Bad Gateway |
PHP-FPM socket mismatch |
Verify socket path in Nginx config matches PHP pool |
| 403 Forbidden |
Incorrect file permissions |
Check ownership and permissions on public_html |
| Database connection error |
Wrong credentials in wp-config.php |
Verify database name, user, and password match |
| SSL certificate error |
DNS not propagated or wrong webroot |
Verify DNS with ping, check webroot path in certbot command |
| Updates fail |
Permissions too restrictive |
Run loosen permissions script before updates |
| Cache not working |
Wrong cache zone name |
Ensure fastcgi_cache name matches keys_zone in nginx.conf |
📚 Additional Resources
Testing Tools
- SSL Labs: https://ssllabs.com/ - Comprehensive SSL/TLS analysis
- HTTP/3 Check: https://http3check.net/ - Verify HTTP/3 support
- GTmetrix: https://gtmetrix.com/ - Performance testing
- WebPageTest: https://webpagetest.org/ - Detailed performance
analysis
Security Testing
- Qualys SSL Test: Verify SSL configuration security
- Security Headers: https://securityheaders.com/ - Check HTTP
security headers
- Mozilla Observatory: https://observatory.mozilla.org/ - Overall
security assessment
🎉 Congratulations!
You have successfully set up an additional WordPress site with enterprise-level
security and performance optimization. Your site now benefits from:
- ✅ Isolated PHP-FPM pool for enhanced security
- ✅ Hardened file permissions and restricted PHP functions
- ✅ SSL/TLS encryption with HTTP/2 and HTTP/3
- ✅ Database privilege restrictions
- ✅ FastCGI and OPcache optimization
- ✅ Rate limiting protection against brute force attacks
- ✅ Automated maintenance scripts for updates